<?php

namespace Xin\Pipeline;

use League\Pipeline\FingersCrossedProcessor;
use League\Pipeline\InterruptibleProcessor;
use League\Pipeline\ProcessorInterface;
use Xin\Pipeline\Contracts\Repository;

/**
 * @mixin Repository
 */
class PipelineManager
{
	/**
	 * @var Repository
	 */
	protected $repository;

	/**
	 * @var array
	 */
	protected static $customHandlerFactories = [];

	/**
	 * @param Repository|null $repository
	 */
	public function __construct(Repository $repository = null)
	{
		$this->repository = $repository ?: new PipeRepository();
	}

	/**
	 * 调用管道流
	 * @param ProcessorInterface $processor
	 * @param mixed $input
	 * @param string $name
	 * @return mixed
	 */
	protected function invokePipeline(ProcessorInterface $processor, $input, $name)
	{
		return $processor->process(
			$input,
			...$this->repository->get($name)
		);
	}

	/**
	 * 线性调度管道器 ->->->->
	 * @param mixed $input
	 * @param string $name
	 * @return mixed
	 */
	public function linear($input, $name = 'global')
	{
		return $this->invokePipeline(
			new FingersCrossedProcessor(),
			$input,
			$name
		);
	}

	/**
	 * 根据描述数据注册pipes
	 * @param array $pipeMetas
	 * @param string $name
	 * @return void
	 */
	public function pipesFromMetas(array $pipeMetas, $name = 'global')
	{
		$pipes = [];
		foreach ($pipeMetas as $meta) {
			if (isset($meta['name'])) {
				$key = $meta['name'];
				$handler = self::makeCustomHandler($key, $meta['config'] ?? null);
			} elseif (isset($meta['handler'])) {
				$handler = $meta['handler'];
			} else {
				throw new \InvalidArgumentException("Meta 'name' or 'handler' is required");
			}

			$pipes[] = $handler;
		}

		$this->repository->pushMany($pipes, $name);
	}

	/**
	 * 线性调度管道器（可中断）->->｜->->
	 * @param mixed $input
	 * @param string $name
	 * @return mixed
	 */
	public function linearInterruptible($input, callable $check, $name = 'global')
	{
		return $this->invokePipeline(
			new InterruptibleProcessor($check),
			$input,
			$name
		);
	}

	/**
	 * 调度中间件管道器 ->-><-<-
	 * @param mixed $input
	 * @param callable $destination
	 * @param string $name
	 * @return mixed
	 */
	public function middleware($input, callable $destination, $name = 'global')
	{
		return $this->invokePipeline(
			new Processor($destination),
			$input,
			$name
		);
	}

	/**
	 * 调度管道到场景器
	 * @param mixed $payload
	 * @param callable $destination
	 * @param string $name
	 * @return mixed
	 */
	public function __invoke($payload, callable $destination, $name = 'global')
	{
		return $this->middleware($payload, $destination, $name);
	}

	/**
	 * @param string $name
	 * @param array $arguments
	 * @return mixed
	 */
	public function __call(string $name, array $arguments)
	{
		return call_user_func_array([$this->repository, $name], $arguments);
	}

	/**
	 * 快速调用中间件管道器
	 * @param mixed $input
	 * @param array|Repository $pipelineStages
	 * @param callable $destination
	 * @return mixed
	 */
	public static function dispatchMiddleware($input, $pipelineStages, callable $destination)
	{
		if (is_array($pipelineStages)) {
			$repository = new PipeRepository();
			$repository->pushMany($pipelineStages);
		} else {
			$repository = $pipelineStages;
		}

		return (new static($repository))->middleware($input, $destination);
	}

	/**
	 * 注册自定义handler工厂
	 * @param string $name
	 * @param \Closure $closure
	 * @return void
	 */
	public static function customHandlerFactory(string $name, \Closure $closure)
	{
		static::$customHandlerFactories[$name] = $closure;
	}

	/**
	 * 构造自定义handler
	 * @param string $name
	 * @param mixed $payload
	 * @return \Closure
	 */
	protected static function makeCustomHandler($name, $payload)
	{
		$factory = static::$customHandlerFactories[$name];
		return $factory($payload);
	}

	/**
	 * 线性调度管道器（来自描述数据） ->->->->
	 * @param mixed $input
	 * @param string $name
	 * @return mixed
	 */
	public static function linearFromMetas(array $pipeMetas, $input, $name = 'global')
	{
		$pipeline = new static();
		$pipeline->pipesFromMetas($pipeMetas, $name);

		return $pipeline->linear($input, $name);
	}

	/**
	 * 线性调度管道器（可中断）（来自描述数据）->->｜->->
	 * @param mixed $input
	 * @param string $name
	 * @return mixed
	 */
	public static function linearInterruptibleFromMetas(array $pipeMetas, $input, $name = 'global')
	{
		$pipeline = new static();
		$pipeline->pipesFromMetas($pipeMetas, $name);

		return $pipeline->linearInterruptible($input, $name);
	}
}
